﻿/*----------------------------------------------------------------
// Copyright (C) 2011 
// 版权所有。 
//
// 文件名：SinaService.cs
// 文件功能描述：
//
// 
// 创建标识：kenter 2011-10-19
// EMAIL:kenter1643@163.com
//
// 修改标识：
// 修改描述：
//
// 修改标识：
// 修改描述：
//----------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OAuth;
using WeiBOAPI.Common;
using System.Web;
using System.Xml.Linq;
using WeiBoUI.Template;
using NLog;
using System.IO;
using System.Text.RegularExpressions;

namespace WeiBOAPI.Sina
{
    public class SinaService
        : OAuthBase, IWeiBoAPI
    {
        private static Logger logger = LogManager.GetCurrentClassLogger(); 
        private string _apiKey = "620740399";//申请的App Key
        private string _apiKeySecret = "17c3dbb0b6f2773bdbe5da922e74ff8f";//申请的App Secret
        private const string REQUEST_TOKEN_URI = @"http://api.t.sina.com.cn/oauth/request_token";
        private const string AUTHORIZE = @"http://api.t.sina.com.cn/oauth/authorize";
        private const string ACCESS_TOKEN = @"http://api.t.sina.com.cn/oauth/access_token";

        public string _oauth_token = "";
        public string _oauth_token_secret = "";
        public string _user_Id = "";
        public string _loginName = "";
        public string _loginPwd = "";

        public Action<string> _ac_Show;
        public Action<SinaUserInfo> _ac_LoginIn;
        public Action<string> _ac_RequestToken;
        public Action<string> _ac_GetToken;

        HttpHelper _httpHelper = new HttpHelper();

        #region 提交业务
        /// <summary>
        /// 提交业务
        /// </summary>
        /// <param name="serviceName">业务路径</param>
        /// <param name="paras">附加参数</param>
        private void DoService(string serviceName, List<QueryParameter> paras, SinaAPI apiType
            , bool isPost = false)
        {
            try
            {
                Uri uri = new Uri("http://api.t.sina.com.cn/" + serviceName);

                string nonce = this.GenerateNonce();
                string timeStamp = this.GenerateTimeStamp();
                string normalizeUrl, normalizedRequestParameters;

                // 签名
                string sig = "";
                string queryString = "";
                

                if (isPost)
                {
                    paras.Add(new QueryParameter("source", _apiKey));
                    sig = this.GenerateSignature(uri, _apiKey, _apiKeySecret, _oauth_token
                        , _oauth_token_secret, "POST", timeStamp, nonce, out normalizeUrl,
                        out normalizedRequestParameters, paras);
                    _httpHelper.DoPostRequest(normalizeUrl, apiType, normalizedRequestParameters
                         + "&oauth_signature=" + sig);
                } 
                else
                {
                    sig = this.GenerateSignature(uri, _apiKey, _apiKeySecret, _oauth_token
                        , _oauth_token_secret, "GET", timeStamp, nonce, out normalizeUrl,
                        out normalizedRequestParameters, paras);
                    sig = HttpUtility.UrlEncode(sig);
                    queryString = normalizedRequestParameters + "&oauth_signature=" + sig;

                    if (queryString.Length > 0)
                    {
                        normalizeUrl += "?";
                    }
                    _httpHelper.DoGetRequest(normalizeUrl + queryString, apiType);
                }
                
            }
            catch (System.Exception ex)
            {
                logger.Error(ex.Message);
            }
            
        } 
        #endregion

        #region 获取令牌
        /// <summary>
        /// 
        /// </summary>
        public void GetRequestToken()
        {
            Uri uri = new Uri(REQUEST_TOKEN_URI);
            string nonce = this.GenerateNonce();//获取随机生成的字符串，防止攻击
            string timeStamp = this.GenerateTimeStamp();//发起请求的时间戳
            string normalizeUrl, normalizedRequestParameters;
            // 签名
            string sig = this.GenerateSignature(uri, _apiKey, _apiKeySecret, string.Empty, string.Empty,
               "GET", timeStamp, nonce, out normalizeUrl, out normalizedRequestParameters
               , new List<QueryParameter>());
            sig = HttpUtility.UrlEncode(sig);
            //构造请求Request Token的url
            string queryString = normalizedRequestParameters + "&oauth_signature=" + sig;

            if (queryString.Length > 0)
            {
                normalizeUrl += "?";
            }

            _httpHelper.DoGetRequest(normalizeUrl + queryString, SinaAPI.RequestToken);
        } 
        #endregion

        #region 验证用户
        /// <summary>
        /// 验证用户
        /// </summary>
        /// <param name="oauth_verifier">令牌号</param>
        public void GetAccessToken(string oauth_verifier)
        {
            Uri uri = new Uri(ACCESS_TOKEN);
            string nonce = this.GenerateNonce();
            string timeStamp = this.GenerateTimeStamp();
            string normalizeUrl, normalizedRequestParameters;

            this.Verifier = oauth_verifier;
            // 签名
            string sig = this.GenerateSignature(uri, _apiKey, _apiKeySecret, _oauth_token, _oauth_token_secret,
            "GET", timeStamp, nonce, out normalizeUrl, out normalizedRequestParameters
               , new List<QueryParameter>());
            sig = HttpUtility.UrlEncode(sig);

            string queryString = normalizedRequestParameters + "&oauth_signature=" + sig;

            if (queryString.Length > 0)
            {
                normalizeUrl += "?";
            }

            //请求Access Token
            _httpHelper.DoGetRequest(normalizeUrl + queryString, SinaAPI.GetAccessToken);

        } 
        #endregion

        #region 获取当前登录用户及其所关注用户的最新微博消息
        /// <summary>
        /// 获取当前登录用户及其所关注用户的最新微博消息
        /// </summary>
        /// <param name="since_id">若指定此参数，则只返回ID比since_id大的微博消息
        /// （即比since_id发表时间晚的微博消息）。 </param>
        /// <param name="max_id">若指定此参数，则返回ID小于或等于max_id的微博消息</param>
        /// <param name="count">指定要返回的记录条数。默认值20，最大值200。</param>
        /// <param name="page">指定返回结果的页码。根据当前登录用户所关注的用户数及这些被关注用户发表的微博数
        /// ，翻页功能最多能查看的总记录数会有所不同，通常最多能查看1000条左右。 </param>
        /// <param name="base_app">是否基于当前应用来获取数据。1为限制本应用微博，0为不做限制。</param>
        /// <param name="featrue">微博类型，0全部，1原创，2图片，3视频，4音乐.
        /// 返回指定类型的微博信息内容。</param>
        public void FriendsTimeLine(long since_id = -1, long max_id = -1, int count = -1
            , int page = -1, int base_app = -1, int featrue = -1)
        {
            try
            {
                Dictionary<string, object> pars = new Dictionary<string, object>();
                pars.Add("since_id", since_id);
                pars.Add("max_id", max_id);
                pars.Add("count", count);
                pars.Add("page", page);
                pars.Add("base_app", base_app);
                pars.Add("featrue", featrue);

                this.DoService("statuses/friends_timeline.xml", SetPars(pars), SinaAPI.FriendsTimeLine);
            }
            catch (System.Exception ex)
            {
                logger.Error(ex.Message);
            }
            
        }

        private List<QueryParameter> SetPars(Dictionary<string, object> pars)
        {
            List<QueryParameter> paras = new List<QueryParameter>();

            foreach (KeyValuePair<string, object> keyValue in pars)
            {
                try
                {
                    if (Convert.ToDecimal(keyValue.Value) > 0)
                    {
                        paras.Add(new QueryParameter(keyValue.Key, Convert.ToString(keyValue.Value)));
                    }
                    
                }
                catch
                {
                    paras.Add(new QueryParameter(keyValue.Key, Convert.ToString(keyValue.Value)));
                }
            }

            return paras;
        }
        #endregion

        #region 获取当前登录用户的微博消息
        /// <summary>
        /// 获取当前登录用户的微博消息
        /// </summary>
        /// <param name="since_id">若指定此参数，则只返回ID比since_id大的微博消息
        /// （即比since_id发表时间晚的微博消息）。 </param>
        /// <param name="max_id">若指定此参数，则返回ID小于或等于max_id的微博消息</param>
        /// <param name="count">指定要返回的记录条数。默认值20，最大值200。</param>
        /// <param name="page">指定返回结果的页码。根据当前登录用户所关注的用户数及这些被关注用户发表的微博数
        /// ，翻页功能最多能查看的总记录数会有所不同，通常最多能查看1000条左右。 </param>
        /// <param name="base_app">是否基于当前应用来获取数据。1为限制本应用微博，0为不做限制。</param>
        /// <param name="featrue">微博类型，0全部，1原创，2图片，3视频，4音乐.
        /// 返回指定类型的微博信息内容。</param>
        public void GetMentions(long since_id = -1, long max_id = -1, int count = -1
            , int page = -1, int base_app = -1, int featrue = -1)
        {
            try
            {
                Dictionary<string, object> pars = new Dictionary<string, object>();
                pars.Add("since_id", since_id);
                pars.Add("max_id", max_id);
                pars.Add("count", count);
                pars.Add("page", page);
                pars.Add("base_app", base_app);
                pars.Add("featrue", featrue);

                this.DoService("statuses/mentions.xml", SetPars(pars), SinaAPI.Mentions);
            }
            catch (System.Exception ex)
            {
                logger.Error(ex.Message);
            }

        }
        #endregion

        #region 获取某用户的微博消息
        /// <summary>
        /// 获取当前登录用户的微博消息
        /// </summary>
        /// <param name="since_id">若指定此参数，则只返回ID比since_id大的微博消息
        /// （即比since_id发表时间晚的微博消息）。 </param>
        /// <param name="max_id">若指定此参数，则返回ID小于或等于max_id的微博消息</param>
        /// <param name="count">指定要返回的记录条数。默认值20，最大值200。</param>
        /// <param name="page">指定返回结果的页码。根据当前登录用户所关注的用户数及这些被关注用户发表的微博数
        /// ，翻页功能最多能查看的总记录数会有所不同，通常最多能查看1000条左右。 </param>
        /// <param name="base_app">是否基于当前应用来获取数据。1为限制本应用微博，0为不做限制。</param>
        /// <param name="featrue">微博类型，0全部，1原创，2图片，3视频，4音乐.
        /// 返回指定类型的微博信息内容。</param>
        public void GetUserNews(string userID, long since_id = -1, long max_id = -1, int count = -1
            , int page = -1, int base_app = -1, int featrue = -1)
        {
            try
            {
                Dictionary<string, object> pars = new Dictionary<string, object>();
                pars.Add("since_id", since_id);
                pars.Add("max_id", max_id);
                pars.Add("count", count);
                pars.Add("page", page);
                pars.Add("base_app", base_app);
                pars.Add("featrue", featrue);

                this.DoService("statuses/user_timeline/" + userID + ".xml"
                    , SetPars(pars), SinaAPI.Mentions);
            }
            catch (System.Exception ex)
            {
                logger.Error(ex.Message);
            }

        }
        #endregion

        #region 用户资料获取
        /// <summary>
        /// 用户资料获取
        /// </summary>
        /// <param name="user_id"></param>
        public void UserShow(string user_id)
        {
            Dictionary<string, object> pars = new Dictionary<string, object>();
            pars.Add("user_id", user_id);

            this.DoService("users/show/" + user_id + ".xml", SetPars(pars), SinaAPI.UserShow);
        } 
        #endregion

        #region 发表一条微博
        /// <summary>
        /// 发表一条微博
        /// </summary>
        /// <param name="status">要发布的微博消息文本内容</param>
        /// <param name="in_reply_to_status_id">要转发的微博消息ID</param>
        /// <param name="lat">纬度。有效范围：-90.0到+90.0，+表示北纬</param>
        /// <param name="_long">经度。有效范围：-180.0到+180.0，+表示东经
        /// 注意：lat和long参数需配合使用，用于标记发表微博消息时所在的地理位置，
        /// 只有用户设置中geo_enabled=true时候地理位置信息才有效。</param>
        /// <param name="annotations">元数据，主要是为了方便第三方应用记录一些适合于自己使用的信息。
        /// 每条微博可以包含一个或者多个元数据。请以json字串的形式提交，字串长度不超过512个字符
        /// ，具体内容可以自定。</param>       
        public void AddNewWB(string status, long in_reply_to_status_id = -1, float lat = -1
            , float _long = -1, string annotations = null)
        {
            Dictionary<string, object> pars = new Dictionary<string, object>();
            pars["status"] = status;
            pars["in_reply_to_status_id"] = in_reply_to_status_id;
            pars["lat"] = lat;
            pars["long"] = _long;
            pars["annotations"] = annotations;

            this.DoService("statuses/update.xml", SetPars(pars), SinaAPI.PostNew, true);
        } 
        #endregion

        #region 获取表情
        /// <summary>
        /// 获取表情
        /// </summary>
        public void GetEmotions()
        {
            if (File.Exists("./Emotions.xml"))
            {
                Global.Instance._g_Emotions = CommonTools.ReadXml<SinaEmotions>("./Emotions.xml");
            }
            else
            {
                _httpHelper.DoGetRequest("http://api.t.sina.com.cn/emotions.xml?source=" + _apiKey
                , SinaAPI.Emotions);
            }
        }
        #endregion

        #region 回调业务接口
        /// <summary>
        /// 回调业务接口
        /// </summary>
        /// <param name="bType"></param>
        /// <param name="content"></param>
        public void DoReturn(SinaAPI apiType, string content)
        {
            try
            {
                switch (apiType)
                {
                    case SinaAPI.RequestToken:
                        {
                            var responseObj = HttpUtility.ParseQueryString(content);

                            if (responseObj[OAuthTokenKey] != null)
                            {
                                _oauth_token = responseObj[OAuthTokenKey];
                            }
                            if (responseObj[OAuthTokenSecretKey] != null)
                            {
                                _oauth_token_secret = responseObj[OAuthTokenSecretKey];
                            }

                            if (_ac_RequestToken != null)
                            {
                                _ac_RequestToken(AUTHORIZE + "?oauth_token=" + _oauth_token
                                + "&oauth_callback=oob");
                            }
                            //CommonTools.OpenIE(AUTHORIZE + "?oauth_token=" + _oauth_token
                            //    + "&oauth_callback=oob");
                        }
                        break;
                    case SinaAPI.GetAccessToken:
                        {
                            var responseObj = HttpUtility.ParseQueryString(content);
                            if (responseObj[OAuthTokenKey] != null)
                            {
                                _oauth_token = responseObj[OAuthTokenKey];
                            }
                            if (responseObj[OAuthTokenSecretKey] != null)
                            {
                                _oauth_token_secret = responseObj[OAuthTokenSecretKey];
                            }
                            if (responseObj["user_id"] != null)
                            {
                                _user_Id = responseObj["user_id"];
                            }

                            SinaLogin sinaLogin = new SinaLogin();
                            sinaLogin.OAuthTokenKey = _oauth_token;
                            sinaLogin.OAuthTokenSecretKey = _oauth_token_secret;
                            sinaLogin.UserID = _user_Id;
                            sinaLogin.LoginUserName = _loginName;
                            sinaLogin.LoginUserPwd = _loginPwd;

                            CommonTools.WriteXml<SinaLogin>(sinaLogin
                                , "./Sina/" + sinaLogin.UserID + ".xml");

                            if (_ac_GetToken != null)
                            {
                                _ac_GetToken(_oauth_token_secret);
                            }
                        }
                        break;
                    case SinaAPI.FriendsTimeLine:
                    case SinaAPI.Mentions:
                    case SinaAPI.UserTimeLine:
                        {
                            TplParse tplParse = new TplParse("./Template/NewsList.tpl");
                            string code = tplParse.TplText;
                            //循环替换
                            List<ForeachObj> msgList = new List<ForeachObj>();

                            XElement xmlPage = XElement.Parse(content);
                            foreach (XElement status in xmlPage.Elements("status"))
                            {
                                ForeachObj msgObj = new ForeachObj();

                                if (status.Element("retweeted_status") != null)
                                {
                                    XElement retweeted_status = status.Element("retweeted_status");
                                    msgObj["HasReweekStates"] = "true";
                                    msgObj["retweeted_created_at"] = CommonTools.SplitDateTime(retweeted_status.Element
                                    ("created_at").Value);
                                    msgObj["retweeted_text"] = retweeted_status.Element("text").Value;
                                    msgObj["retweeted_source"] = retweeted_status.Element("source").Value;
                                    msgObj["retweeted_user_screen_name"] = retweeted_status.Element("user")
                                        .Element("screen_name").Value;
                                    msgObj["retweeted_user_id"] = retweeted_status.Element("user")
                                        .Element("id").Value;
                                    msgObj["retweeted_user_profile_image_url"] = GetImageUrl(retweeted_status.Element("user")
                                        .Element("profile_image_url").Value, ImageType.samll);

                                    msgObj["retweeted_HasPic"] = "false";
                                    if (retweeted_status.Element("thumbnail_pic") != null)
                                    {
                                        msgObj["retweeted_HasPic"] = "true";
                                        msgObj["retweeted_thumbnail_pic"] = retweeted_status.Element("thumbnail_pic").Value;
                                        msgObj["retweeted_original_pic"] = retweeted_status.Element("original_pic").Value;
                                    }
                                }
                                else
                                {
                                    msgObj["HasReweekStates"] = "false";
                                }

                                msgObj["created_at"] = CommonTools.SplitDateTime(status.Element
                                    ("created_at").Value);
                                msgObj["text"] = status.Element("text").Value;
                                msgObj["source"] = status.Element("source").Value;
                                msgObj["user_screen_name"] = status.Element("user")
                                    .Element("screen_name").Value;
                                msgObj["user_id"] = status.Element("user")
                                    .Element("id").Value;
                                msgObj["user_profile_image_url"] = GetImageUrl(status.Element("user")
                                    .Element("profile_image_url").Value, ImageType.samll);

                                msgObj["HasPic"] = "false";
                                if (status.Element("thumbnail_pic") != null)
                                {
                                    msgObj["HasPic"] = "true";
                                    msgObj["thumbnail_pic"] = status.Element("thumbnail_pic").Value;
                                    msgObj["original_pic"] = status.Element("original_pic").Value;
                                }

                                if (status.Elements("annotations").Count() > 0)
                                {

                                    msgObj["HasPic"] = "true";
                                }

                                msgList.Add(msgObj);
                            }

                            tplParse.SetList(msgList, "MessageList", ref code);
                            tplParse.SetIf(ref code);
                            SetEmotions(ref code);
                            _ac_Show(code);
                        }
                        break;
                    case SinaAPI.UserShow:
                        {
                            XElement xmlPage = XElement.Parse(content);
                            if (_ac_LoginIn != null)
                            {
                                SinaUserInfo user = new SinaUserInfo();
                                user.ScreenName = xmlPage.Element("screen_name").Value;
                                user.HeadImage = GetImageUrl(xmlPage.Element("profile_image_url").Value,
                                    ImageType.middle);
                                user.FollowersCount = xmlPage.Element("followers_count").Value;
                                user.FriendsCount = xmlPage.Element("friends_count").Value;
                                user.StatusesCount = xmlPage.Element("statuses_count").Value;
                                user.Url = xmlPage.Element("url").Value;
                                user.Id = xmlPage.Element("id").Value;

                                Global.Instance._g_SinaUser = user;

                                _ac_LoginIn(user);
                            }
                        }
                        break;
                    case SinaAPI.Emotions:
                        {
                            XElement xmlPage = XElement.Parse(content);
                            Global.Instance._g_Emotions.Emotions = new List<Emotion>();
                            foreach (XElement emotion in xmlPage.Elements("emotion"))
                            {
                                Emotion et = new Emotion();
                                et.Phrase = emotion.Element("phrase").Value;
                                et.EmotionUrl = emotion.Element("url").Value;
                                Global.Instance._g_Emotions.Emotions.Add(et);
                            }
                            CommonTools.WriteXml<SinaEmotions>(Global.Instance._g_Emotions
                                , "./Emotions.xml");
                        }
                        break;
                    default: break;
                }
            }
            catch (System.Exception ex)
            {
                logger.Error(ex.Message);
            }
            
        } 
        #endregion

        #region 获取头像URl
        /// <summary>
        /// 获取头像URl
        /// </summary>
        /// <param name="strValue"></param>
        /// <param name="imageUrlEntity"></param>
        /// <returns></returns>
        public static string GetImageUrl(string strValue, ImageType imageUrlEntity)
        {
            return strValue.Replace("/50/", "/" + (int)imageUrlEntity + "/");
        } 
        #endregion

        #region 设置表情
        /// <summary>
        /// 设置表情
        /// </summary>
        /// <param name="code"></param>
        public void SetEmotions(ref string code)
        {
            string pattern = "\\[(?<TagName>[\\s\\S]*?)\\]";
            MatchCollection matchs = Regex.Matches(code, pattern, RegexOptions.Multiline);
            Dictionary<string, Match> mas = new Dictionary<string, Match>();
            foreach (Match mc in matchs)
            {
                if (!mas.ContainsKey(mc.ToString()))
                {
                    mas.Add(mc.ToString(), mc);
                }
                
            }

            foreach (KeyValuePair<string, Match>mc in mas)
            {
                try
                {
                    string face = mc.Value.ToString();
                    string rePlace = "<img src=\"" + Global.Instance._g_Emotions.Emotions
                        .Find(rs => rs.Phrase.Equals(face)).EmotionUrl + "\" />";
                    code = code.Replace(face, rePlace);
                }
                catch (System.Exception ex)
                {
                    logger.Error(ex.Message);
                }
            }
            
        } 
        #endregion

    }

    #region 新浪API业务类型
    /// <summary>
    /// 新浪API业务类型
    /// </summary>
    public enum SinaAPI
    {
        RequestToken,    //获取令牌
        GetAccessToken,  //验证用户
        FriendsTimeLine, //微博最新消息
        UserTimeLine,    //某用户最新微博
        UserShow,        //用户信息
        PostNew,         //发表微博
        Emotions,        //表情  
        Mentions        //当前用户发布的微博
    }

    public enum ImageType
    {
        /// <summary>
        /// 小尺寸30x30像素
        /// </summary>
        samll = 30,
        /// <summary>
        /// 中尺寸50x50像素
        /// </summary>
        middle = 50,
        /// <summary>
        /// 大尺寸180x180像素
        /// </summary>
        big = 180
    }
    #endregion
}
